package cn.com.generaldata.os.service.impl;

import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.impl.sql.SqlTemplate;
import org.nutz.lang.Lang;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import cn.com.generaldata.jsme_indicator_system.entity.Dustry;
import cn.com.generaldata.jsme_indicator_system.entity.IndicatorConsumpt;
import cn.com.generaldata.jsme_indicator_system.util.InverseMatrix;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableMap;

/**
 * 国民经济产业分类直接消耗系数service.
 */
@Service
public class IndicatorConsumptService {

	@Autowired
	private DustryService dustryService;

	@Autowired
	private Dao dao;

	// 直接消耗系数逆距阵的缓存
	private Cache<Integer, double[][]> consumptInverseMatrixCache = CacheBuilder.newBuilder()
			.expireAfterAccess(1, TimeUnit.DAYS)
			.build();

	// 直接消耗系数逆距阵之和平均值的缓存
	private Cache<Integer, Double> consumptInverseMatrixAvgCache = CacheBuilder.newBuilder()
			.expireAfterAccess(1, TimeUnit.DAYS)
			.build();

	/**
	 * 检查是否存在该年份的直接消耗系数数据
	 * 
	 * @param year
	 * @return
	 */
	public boolean checkHasData(int year) {
		Cnd cnd = Cnd.where("year", "=", year);
		return dao.count(IndicatorConsumpt.class, cnd) > 0 ? true : false;
	}

	/**
	 * 获取直接消耗系数表的逆距阵
	 * 
	 * @param year 年份
	 * @return
	 */
	public double[][] getConsumptInverseMatrix(final int year) {
		double[][] inverseMatrix = null;
		try {
			inverseMatrix = consumptInverseMatrixCache.get(year, new Callable<double[][]>() {
				@Override
				public double[][] call() {
					double[][] matrix = getConsumptMatrix(year);
					matrix = diagonalMatrixElemMinusOne(matrix);
					return InverseMatrix.inverse(matrix);
				}
			});
		} catch (ExecutionException e) {
			throw Lang.makeThrow("获取直接消耗系数逆距阵错误,year:%d", year, e);
		}

		return inverseMatrix;
	}

	/**
	 * 获取逆距阵之和平均值
	 * 
	 * @param year
	 * @return
	 */
	public double getConsumptInverseMatrixAvg(final int year) {
		double avg;
		try {
			avg = consumptInverseMatrixAvgCache.get(year, new Callable<Double>() {
				@Override
				public Double call() {
					double[][] inverseMatrix = getConsumptInverseMatrix(year);

					double inverseMatrixSum = 0;
					for (double[] row : inverseMatrix) {
						for (double column : row) {
							inverseMatrixSum += column;
						}
					}
					return inverseMatrixSum / inverseMatrix.length;
				}
			});
		} catch (ExecutionException e) {
			throw Lang.makeThrow("获取直接消耗系数逆距阵之和平均值错误,year:%d", year, e);
		}

		return avg;
	}

	/**
	 * 根据年份构造直接消耗表两维距阵.
	 * 
	 * @param year
	 * @return
	 */
	private double[][] getConsumptMatrix(int year) {
		List<Dustry> dustries = dustryService.getDustrys(year);
		double[][] matrix = new double[dustries.size()][dustries.size()];
		for (int row = 0, rowCount = dustries.size(); row < rowCount; row++) {
			Dustry rowDustry = dustries.get(row);

			String sql = "SELECT \r\n" +
					"		tic.* \r\n" +
					"	FROM tbl_indicator_consumpt tic INNER JOIN tbl_dustry d ON tic.dustry_id = d.id\r\n" +
					"	WHERE tic.dustry_id = @dustryId AND tic.year = @year " +
					"	ORDER BY d.code ASC";
			Map<String, Object> params = ImmutableMap.<String, Object> of("dustryId", rowDustry.getId(), "year", year);

			SqlTemplate sqlTemplate = new SqlTemplate(dao);
			List<IndicatorConsumpt> ics = sqlTemplate.query(sql, params, dao.getEntity(IndicatorConsumpt.class));

			for (int column = 0, columnCount = ics.size(); column < columnCount; column++) {
				IndicatorConsumpt consumpt = ics.get(column);
				if (consumpt != null) {
					matrix[row][column] = consumpt.getConsumptValue();
				}
			}
		}
		return matrix;
	}

	/**
	 * 将距阵的对角线元素减1.
	 * 
	 * @param matrix
	 * @return
	 */
	private double[][] diagonalMatrixElemMinusOne(double[][] matrix) {
		for (int row = 0, rowCount = matrix.length; row < rowCount; row++) {
			for (int column = 0, columnCount = matrix[row].length; column < columnCount; column++) {
				if (row == column) {
					matrix[row][column] = 1 - matrix[row][column];
				}
			}
		}
		return matrix;
	}
}
